﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Emlyn.AudioToVideo.JobProcessorLib.Interfaces;
using wandev.StateMachine.core;

namespace Emlyn.AudioToVideo.JobProcessorLib.core
{
    public abstract class JobBase: IJob, IDisposable
    {
        #region private members

        string _jobID;

        StateMachine _StateMachine;
        Dictionary<Pair<State, Condition>, State> _transitions;
        State _stateInit, _stateHalt;
        State _stateCreated, _stateStarting, _stateInProgress, _stateSucceeded, _stateFailed, _stateFinal, _stateCancelled, _stateStopJobForHalt;
        Condition _condStart, _condCancel, _condSuccess, _condFailure, _condProgressUpdate;

        object _privateSyncObj;
        int _progress;
        string _errorMessage;
        DateTime _lastStateChange;
        string _jobDetails;

        private void _StateMachine_OnNewState(object sender, NewStateEventArgs e)
        {
            lock(_privateSyncObj)
            {
                _lastStateChange = DateTime.UtcNow;
            }

            if (e.NewState.Equals(_stateCreated))
            {
                // do nothing
            }
            else if (e.NewState.Equals(_stateStarting))
            {
                try
                {
                    Progress = 0;
                    SetErrorMessage(null);

                    StartJob();

                    _StateMachine.RaiseCondition(StateMachine.ProceedCondition);
                }
                catch (Exception ex)
                {
                    SetErrorMessage("Can't Start: " + ex.Message);
                    _StateMachine.RaiseCondition(_condFailure);
                }
            }
            else if (e.NewState.Equals(_stateInProgress))
            {
                // here is where we can call an UpdateProgress routine to tell someone about the progress update
            }
            else if (e.NewState.Equals(_stateFailed))
            {
            }
            else if (e.NewState.Equals(_stateSucceeded))
            {
            }
            else if (e.NewState.Equals(_stateCancelled))
            {
                try
                {
                    SetErrorMessage("User Cancelled Job");
                    StopJob(false);
                }
                catch (Exception ex)
                {
                    SetErrorMessage("Exception cancelling job: " + ex.Message);
                    _StateMachine.RaiseCondition(_condFailure);
                }
            }
            else if (e.NewState.Equals(_stateStopJobForHalt))
            {
                try
                {
                    StopJob(true);
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Trace.Fail("Failed during StopJobForHalt", ex.ToString());
                }
            }
            else if (e.NewState.Equals(_stateFinal))
            {
                // here do nothing (it's the halting state, we're cleaning up
            }
        }

        private void ConstructTransitions()
        {
            _privateSyncObj = new object();

            _transitions = new Dictionary<Pair<State, Condition>, State>();

            _stateCreated = new State("Created");
            _stateStarting = new State("Starting");
            _stateInProgress = new State("In Progress");
            _stateSucceeded = new State("Succeeded");
            _stateFailed = new State("Failed");
            _stateCancelled = new State("Cancelled");
            _stateFinal = new State("Final");
            _stateStopJobForHalt = new State("StopJobForHalt");

            _condStart = new Condition("Start");
            _condCancel = new Condition("Cancel");
            _condSuccess = new Condition("Success");
            _condFailure = new Condition("Failure");
            _condProgressUpdate = new Condition("ProgressUpdate");

            _transitions.Add(new Pair<State, Condition>(_stateCreated, _condStart), _stateStarting);
            _transitions.Add(new Pair<State, Condition>(_stateCreated, _condCancel), _stateCancelled);
            _transitions.Add(new Pair<State, Condition>(_stateCreated, StateMachine.StopCondition), _stateFinal);

            _transitions.Add(new Pair<State, Condition>(_stateStarting, StateMachine.ProceedCondition), _stateInProgress);
            _transitions.Add(new Pair<State, Condition>(_stateStarting, _condCancel), _stateCancelled);
            _transitions.Add(new Pair<State, Condition>(_stateStarting, _condFailure), _stateFailed);
            _transitions.Add(new Pair<State, Condition>(_stateStarting, StateMachine.StopCondition), _stateFinal);

            _transitions.Add(new Pair<State, Condition>(_stateInProgress, _condProgressUpdate), _stateInProgress);
            _transitions.Add(new Pair<State, Condition>(_stateInProgress, _condFailure), _stateFailed);
            _transitions.Add(new Pair<State, Condition>(_stateInProgress, _condSuccess), _stateSucceeded);
            _transitions.Add(new Pair<State, Condition>(_stateInProgress, _condCancel), _stateCancelled);
            _transitions.Add(new Pair<State, Condition>(_stateInProgress, StateMachine.StopCondition), _stateStopJobForHalt);

            _transitions.Add(new Pair<State, Condition>(_stateFailed, _condStart), _stateStarting);
            _transitions.Add(new Pair<State, Condition>(_stateFailed, StateMachine.StopCondition), _stateFinal);

            _transitions.Add(new Pair<State, Condition>(_stateSucceeded, _condStart), _stateStarting);
            _transitions.Add(new Pair<State, Condition>(_stateSucceeded, StateMachine.StopCondition), _stateFinal);

            _transitions.Add(new Pair<State, Condition>(_stateStopJobForHalt, StateMachine.ProceedCondition), _stateFinal);

            _transitions.Add(new Pair<State, Condition>(_stateCancelled, _condStart), _stateStarting);
            _transitions.Add(new Pair<State, Condition>(_stateCancelled, StateMachine.StopCondition), _stateFinal);

            _stateInit = _stateCreated;
            _stateHalt = _stateFinal;
        }

        #endregion

        #region Constructor

        public JobBase()
        {
            Init(Guid.NewGuid().ToString());
        }

        public JobBase(string aJobID)
        {
            Init(aJobID);
        }

        private void Init(string aJobID)
        {
            _jobID = aJobID;
            _jobDetails = null;
            _lastStateChange = DateTime.UtcNow;

            ConstructTransitions();

            _StateMachine = new StateMachine(_transitions, _stateInit, _stateHalt);

            _StateMachine.OnNewState += new EventHandler<NewStateEventArgs>(_StateMachine_OnNewState);

            _StateMachine.Start();
        }

        #endregion

        #region protected members

        protected int Progress
        {
            get
            {
                lock (_privateSyncObj)
                {
                    return _progress;
                }
            }
            set
            {
                lock (_privateSyncObj)
                {
                    _progress = value;
                }
            }
        }

        protected void SetErrorMessage(string aErrorMessage)
        {
            lock (_privateSyncObj)
            {
                _errorMessage = aErrorMessage;
            }
        }

        protected void SignalFailure()
        {
            _StateMachine.RaiseCondition(_condFailure);
        }

        protected void SignalSuccess()
        {
            _StateMachine.RaiseCondition(_condSuccess);
        }

        protected void SignalCancel()
        {
            _StateMachine.RaiseCondition(_condCancel);
        }

        protected void SignalProgressUpdate()
        {
            _StateMachine.RaiseCondition(_condProgressUpdate);
        }

        #endregion

        #region abstract methods, must be overridden

        protected abstract bool CanStart();

        protected abstract void StartJob();

        protected abstract void StopJob(bool aHalting);

        protected virtual void DoDispose()
        {
            // by default do nothing, overriding is optional.
        }

        #endregion

        #region IJob Members

        public string JobID
        {
            get { return _jobID.ToString(); }
        }

        public string JobDetails
        {
            get
            {
                lock (_privateSyncObj)
                {
                    return _jobDetails;
                }
            }
            set
            {
                lock (_privateSyncObj)
                {
                    _jobDetails = value;
                }
            }
        }

        public JobState State
        {
            get 
            {
                JobState retval = JobState.InternalError;

                if (_StateMachine != null)
                {
                    State lcurrentState = _StateMachine.CurrentState;

                    if (lcurrentState.Equals(_stateCreated))
                    {
                        retval = JobState.Created;
                    }
                    else if (lcurrentState.Equals(_stateStarting))
                    {
                        retval = JobState.Started;
                    }
                    else if (lcurrentState.Equals(_stateInProgress))
                    {
                        retval = JobState.InProgress;
                    }
                    else if (lcurrentState.Equals(_stateCancelled))
                    {
                        retval = JobState.Failed;
                    }
                    else if (lcurrentState.Equals(_stateFailed))
                    {
                        retval = JobState.Failed;
                    }
                    else if (lcurrentState.Equals(_stateSucceeded))
                    {
                        retval = JobState.Success;
                    }
                }

                return retval;
            }
        }

        public DateTime LastStateChange
        {
            get 
            {
                lock (_privateSyncObj)
                {
                    return _lastStateChange;
                }
            }
        }

        public int ProgressAmount
        {
            get { return Progress; }
        }

        public string ErrorMessage
        {
            get 
            {
                lock (_privateSyncObj)
                {
                    return _errorMessage;
                }
            }
        }

        public int ResultCode
        {
            get 
            {
                return 0; // currently unsupported
            }
        }

        public void Start()
        {
            if (_StateMachine != null)
            {
                _StateMachine.RaiseCondition(_condStart);
            }
        }

        public void Cancel()
        {
            if (_StateMachine != null)
            {
                _StateMachine.RaiseCondition(_condCancel);
            }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            DoDispose();

            if (_StateMachine != null)
            {
                _StateMachine.Dispose();
                _StateMachine = null;
            }
        }

        #endregion
    }
}
